package managedbean;

import entity.TouristAccountEntity;
import entity.FnBBranchEntity;
import entity.FnBProductEntity;
import entity.FnBReservationEntity;
import entity.FoodTypeEntity;
import entity.MealEntity;
import entity.ProductEntity;
import entity.ReservationEntity;
import entity.ReservedMealEntity;
import entity.ReservedSetMealEntity;
import entity.ReservedTableEntity;
import entity.SetMealEntity;
import entity.TableTypeEntity;
import java.io.IOException;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.PhaseEvent;
import javax.faces.model.SelectItem;
import javax.mail.MessagingException;
import org.primefaces.event.SelectEvent;
import session.stateful.BranchSessionBeanStateful;
import session.stateful.PayPalSessionBean;
import session.stateful.ProductSessionBean;
import session.stateful.ReservationSessionBean;

@ManagedBean
@SessionScoped
public class ReservationManagerBean {

    @EJB
    private ReservationSessionBean reservationSessionBean;
    @EJB
    private ProductSessionBean productSessionBean;
    @EJB
    private BranchSessionBeanStateful branchSessionBean;
    @EJB
    private PayPalSessionBean paypalSessionBean;
    private TouristAccountEntity tourist;
    private FnBBranchEntity selectedBranch;
    private FnBReservationEntity reservation;
    private long branchId;
    /*
     * Common attributes for reservation
     */
    private boolean createTable = false;
    private double depositForTable;
    //total and discount bill of this reservation
    private double totalBill;
    private double discountBill;
    //is the usual eating time for tourist in this restaurant
    int interval;
    //the available timeslot for this tourist to book
    private List<SelectItem> availSlot;
    //time slot chosen by the tourist
    private int timeInput;
    private int count = 0;
    private int count1 = 0;
    private int index = 0;
    private boolean finish;
    private boolean reloadPage;
    private boolean makeReservation;
    private FacesContext ctx = FacesContext.getCurrentInstance();
    /*
     * End common attributes for reservation 
     */
    /*
     * Product-related attributes
     */
    private List<ProductEntity> listOfProduct;
    //used to put the meals into approriate categories. Set Meal will be included at the end
    private List<List<FnBProductEntity>> listOfCategory;
    private List<FoodTypeEntity> listOfFoodType;
    //array to store the choice of product for each food type
    private FnBProductEntity[] selectedProduct;
    private List<List<FnBProductEntity>> choiceList;
    //price list for each product
    private Map<Long, Double> priceList;
    //map the name of the food with the corresponding position in the array
    private Map<String, Integer> foodMap;
    private FnBProductEntity meal;
    //selected quantity for the specific meal
    private int quantity;
    //The number of food types
    private int numOfType;
    //selected quantity for meal/set meal
    private Map<Long, Integer> quantityList;
    /*
     * End product-related attributes
     */
    /*
     * Table-related attributes
     */
    //the list of table types in this restaurant
    private List<TableTypeEntity> tableList;
    //to store the available tables of each table type
    //for visitor to select
    private List[] quantityAvail;
    private boolean reloadTable = true;
    private int[][][] capacity;
    private Map<Long, Integer> tablePos;
    //store the quantity selected by tourist for each table type
    private int[] selectedQuantity;
    /*
     * End table-related attributes
     */
    /*
     * Attributes for restaurant to report shows and no shows
     */
    private FnBReservationEntity[] selectedReservations_status;
    private FnBReservationEntity[] selectedReservations_confirm;
    private FnBReservationEntity selectedReservation;
    private List<FnBReservationEntity> fandBs;
    private List<FnBReservationEntity> fandBShow;
    private List<FnBReservationEntity> fandBNoShow;
    private List<FnBReservationEntity> fandBConfirmed;
    private List<FnBReservationEntity> fandBRejected;
    private List<FnBReservationEntity> allFandB;
    private List<FnBReservationEntity> pendingConfirmations;
    private Date date;
    private String option;
    private List<String> options = new ArrayList<String>();
    /*
     * End of attributes for restaurant to report shows and no shows 
     */
    /*
     * Attributes for change and cancel Reservation
     */
    private List<FnBReservationEntity> listOfReservation;
    private List<ReservedTableEntity> listOfReservedTable;
    private List<ReservedMealEntity> listOfReservedMeal;
    private List<ReservedSetMealEntity> listOfReservedSetMeal;
    private boolean changeReservation;
    private long previousReservationId;
    private double previousDeposit;
    /*
     * End attributes for change and cancel Reservation
     */

    public List<FnBReservationEntity> getPendingConfirmations() {
        pendingConfirmations = new ArrayList<FnBReservationEntity>();
        List<ReservationEntity> tempList = reservationSessionBean.getReservationListByBranchId(selectedBranch.getId());
        for (Object obj : tempList) {
            if (obj instanceof FnBReservationEntity) {
                FnBReservationEntity temp = (FnBReservationEntity) obj;
                if (temp.getConfirmation().equalsIgnoreCase("Pending")) {
                    pendingConfirmations.add(temp);
                }
            }
        }
        setOptions(new ArrayList<String>());
        getOptions().add("Confirm");
        getOptions().add("Reject");
        return pendingConfirmations;
    }

    public void initViewPendingConfirmation(PhaseEvent event) {
        selectedBranch = (FnBBranchEntity) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("selectedBranch");
    }

    public List<FnBReservationEntity> getAllFandB() {
        allFandB = new ArrayList<FnBReservationEntity>();
        selectedBranch = (FnBBranchEntity) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("selectedBranch");
        List<ReservationEntity> tempList = reservationSessionBean.getReservationListByBranchId(selectedBranch.getId());
        for (Object obj : tempList) {
            if (obj instanceof FnBReservationEntity) {
                FnBReservationEntity temp = (FnBReservationEntity) obj;
                allFandB.add(temp);
            }
        }
        return allFandB;
    }

    public void initUpdateTouristStatus(PhaseEvent event) {
    }

    public void fandBListActionListener(ActionEvent event) throws IOException {
        selectedBranch = (FnBBranchEntity) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("selectedBranch");
        getFandBList();
    }

    public void getFandBList() throws IOException {
        fandBs = new ArrayList<FnBReservationEntity>();
        List<ReservationEntity> tempList = reservationSessionBean.getReservationListByBranchId(selectedBranch.getId());
        for (Object obj : tempList) {
            if (obj instanceof FnBReservationEntity) {
                FnBReservationEntity temp = (FnBReservationEntity) obj;
                if ((temp.getDateReserved().compareTo(getDate()) <= 0) && temp.getStatus() != null && (temp.getStatus().equalsIgnoreCase("Pending")) && temp.getConfirmation().equalsIgnoreCase("Confirmed")) {
                    fandBs.add((FnBReservationEntity) obj);
                }
            }
        }
        String serverName = ctx.getExternalContext().getRequestServerName();
        int serverPort = (Integer) ctx.getExternalContext().getSessionMap().get("serverPort");
        String contextPath = ctx.getExternalContext().getRequestContextPath();
        ctx.getExternalContext().redirect("http://" + serverName + ":" + serverPort + contextPath + "/stakeholder/StakeholderUpdateTouristStatus.xhtml");
    }

    public void updateStatus(ActionEvent event) throws IOException, MessagingException {
        fandBShow = new ArrayList<FnBReservationEntity>();
        fandBNoShow = new ArrayList<FnBReservationEntity>();
        for (int i = 0; i < selectedReservations_status.length; i++) {
            FnBReservationEntity temp = selectedReservations_status[i];
            if (temp != null) {
                reservationSessionBean.toShow(temp.getId());
                fandBs.remove(temp);
                temp.setStatus("Show");
                fandBShow.add(temp);
            }
        }
        for (Object o : fandBs) {
            FnBReservationEntity temp = (FnBReservationEntity) o;
            reservationSessionBean.toNoShow(temp.getId());
            temp.setStatus("No Show");
            fandBNoShow.add(temp);
        }
        FacesContext.getCurrentInstance().getExternalContext().redirect("stakeholder/StakeholderViewTouristStatusUpdateResult.xhtml");
    }

    public void toConfirmed(ActionEvent event) throws IOException, MessagingException {

        fandBConfirmed = new ArrayList<FnBReservationEntity>();

        for (Object o : selectedReservations_confirm) {
            FnBReservationEntity temp = (FnBReservationEntity) o;

            reservationSessionBean.toConfirmed(temp.getId());
            temp.setConfirmation("Confirmed");
            getFandBConfirmed().add(temp);

        }
        String serverName = ctx.getExternalContext().getRequestServerName();
        int serverPort = (Integer) ctx.getExternalContext().getSessionMap().get("serverPort");
        String contextPath = ctx.getExternalContext().getRequestContextPath();
        ctx.getExternalContext().redirect("http://" + serverName + ":" + serverPort + contextPath + "stakeholder/StakeholderViewConfirmationUpdateResult.xhtml");
    }

    public void toRejected(ActionEvent event) throws IOException, MessagingException {
        fandBRejected = new ArrayList<FnBReservationEntity>();
        for (Object o : selectedReservations_confirm) {
            FnBReservationEntity temp = (FnBReservationEntity) o;

            reservationSessionBean.toRejected(temp.getId());
            temp.setConfirmation("Rejected");
            getFandBRejected().add(temp);

        }
        String serverName = ctx.getExternalContext().getRequestServerName();
        int serverPort = (Integer) ctx.getExternalContext().getSessionMap().get("serverPort");
        String contextPath = ctx.getExternalContext().getRequestContextPath();
        ctx.getExternalContext().redirect("http://" + serverName + ":" + serverPort + contextPath + "stakeholder/StakeholderViewConfirmationUpdateResult.xhtml");
    }

    public List<FnBReservationEntity> getFandBs() {
        return fandBs;
    }

    public void setFandBs(List<FnBReservationEntity> fandBs) {
        this.setFandBs(fandBs);
    }

    public List<FnBReservationEntity> getFandBShow() {
        return fandBShow;
    }

    public void setFandBShow(List<FnBReservationEntity> fandBShow) {
        this.fandBShow = fandBShow;
    }

    public List<FnBReservationEntity> getFandBNoShow() {
        return fandBNoShow;
    }

    public void setFandBNoShow(List<FnBReservationEntity> fandBNoShow) {
        this.fandBNoShow = fandBNoShow;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public void setAllFandB(List<FnBReservationEntity> allFandB) {
        this.allFandB = allFandB;
    }

    public FnBReservationEntity getSelectedReservation() {
        return selectedReservation;
    }

    public void setSelectedReservation(FnBReservationEntity selectedReservation) {
        this.selectedReservation = selectedReservation;
    }

    public String getOption() {
        return option;
    }

    public void setOption(String option) {
        this.option = option;
    }

    public List<String> getOptions() {
        return options;
    }

    public void setOptions(List<String> options) {
        this.options = options;
    }

    public List<FnBReservationEntity> getFandBConfirmed() {
        return fandBConfirmed;
    }

    public void setFandBConfirmed(List<FnBReservationEntity> fandBConfirmed) {
        this.fandBConfirmed = fandBConfirmed;
    }

    public List<FnBReservationEntity> getFandBRejected() {
        return fandBRejected;
    }

    public void setFandBRejected(List<FnBReservationEntity> fandBRejected) {
        this.setFandBRejected(fandBRejected);
    }

    public FnBReservationEntity[] getSelectedReservations_status() {
        return selectedReservations_status;
    }

    public void setSelectedReservations_status(FnBReservationEntity[] selectedReservations_status) {
        this.selectedReservations_status = selectedReservations_status;
    }

    public FnBReservationEntity[] getSelectedReservations_confirm() {
        return selectedReservations_confirm;
    }

    public void setSelectedReservations_confirm(FnBReservationEntity[] selectedReservations_confirm) {
        this.selectedReservations_confirm = selectedReservations_confirm;
    }

    public ReservationManagerBean() {
        listOfCategory = new ArrayList<List<FnBProductEntity>>();
        choiceList = new ArrayList<List<FnBProductEntity>>();
        reloadPage = true;
        reloadTable = true;
        foodMap = new HashMap<String, Integer>();
        priceList = new HashMap<Long, Double>();
        quantityList = new HashMap<Long, Integer>();
        reservation = new FnBReservationEntity();
        availSlot = new ArrayList();
        quantity = 0;
    }

    //method for meal and set meal reservation
    public void init(PhaseEvent event) {
        if (reloadTable) {
            reservation = new FnBReservationEntity();
            timeInput = 0;
            setListOfFoodType(productSessionBean.getFoodType());
            System.out.println(listOfFoodType.size());
            totalBill = 0;
            discountBill = 0;
            reloadTable = false;
            quantityList = new HashMap<Long, Integer>();
        }
    }

    //load list of products offered by the restaurant on a specific date as well as the corresponding price
    public void initProductList(PhaseEvent event) {
        if (reloadPage) {
            String temp = FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("branchId") + "";
            branchId = Long.parseLong(temp.trim());

            populateProductList();
            //only load this method once
            reloadPage = false;
        }
    }

    public void populateProductList() {
        setListOfProduct(productSessionBean.getProductForReservation(branchId, reservation.getDateReserved()));

        numOfType = getListOfFoodType().size();
        selectedProduct = new FnBProductEntity[numOfType + 1];

        //Initialize them to zero
        choiceList = new ArrayList<List<FnBProductEntity>>();
        foodMap = new HashMap<String, Integer>();
        listOfCategory = new ArrayList<List<FnBProductEntity>>();
        priceList = new HashMap<Long, Double>();
        //Initialize for Meals
        for (int i = 0; i < numOfType; i++) {
            getListOfCategory().add(new ArrayList<FnBProductEntity>());
            choiceList.add(new ArrayList<FnBProductEntity>());
            foodMap.put(listOfFoodType.get(i).getName(), i);
        }

        //this one is for Set Meal
        choiceList.add(new ArrayList<FnBProductEntity>());
        getListOfCategory().add(new ArrayList<FnBProductEntity>());
        //no need to put the Set Meal into foodMap as it will always be in the last position

        //put the meal and set meal into their correct positions in listOfProduct
        for (int i = 0; i < getListOfProduct().size(); i++) {
            int pos = 0;
            if (getListOfProduct().get(i) instanceof MealEntity) {
                MealEntity mealTemp = (MealEntity) getListOfProduct().get(i);
                for (; pos < numOfType; pos++) {
                    FoodTypeEntity food1 = mealTemp.getType();
                    FoodTypeEntity food2 = getListOfFoodType().get(pos);
                    if (food1.equals(food2)) {
                        break;
                    }
                }
                getListOfCategory().get(pos).add(mealTemp);
            } else {
                getListOfCategory().get(numOfType).add((SetMealEntity) getListOfProduct().get(i));
            }

            //finding price from price control
            priceList.put(listOfProduct.get(i).getId(), getOnlinePrice(listOfProduct.get(i).getId(),
                    reservation.getDateReserved(), reservation.getTimeBegin().getHours()));
        }
    }

    //return the online price for a meal/setmeal
    private double getOnlinePrice(long productId, Date dateReserved, int time) {
        return productSessionBean.getOnlinePrice(productId, dateReserved, time);
    }

    public void onRowSelect(SelectEvent event) {
        meal = (FnBProductEntity) event.getObject();
        if (quantityList.get(meal.getId()) != null) {
            quantity = quantityList.get(meal.getId());
        } else {
            quantity = 0;
        }
        for (int i = 0; i < selectedProduct.length; i++) {
            if (selectedProduct[i] != null && !selectedProduct[i].equals(meal)) {
                selectedProduct[i] = null;
            }
        }
    }

    //add this meal/set meal to the shopping cart of the tourist
    public void addToList(ActionEvent event) {
        if (meal != null && meal.getId() != null) {
            if (quantity <= 0) {
                FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Quantity must be more than 0 ", "");
                FacesContext.getCurrentInstance().addMessage(null, msg);
                return;
            }

            int pos = findPosition(meal);
            choiceList.get(pos).add(meal);
            quantityList.put(meal.getId(), quantity);
            discountBill += priceList.get(meal.getId()) * quantityList.get(meal.getId());
            totalBill += meal.getOriginalPrice() * quantityList.get(meal.getId());
            reservation.setDeposit(computeDeposit());
            FacesMessage msg = new FacesMessage("Added ", meal.getName());
            FacesContext.getCurrentInstance().addMessage(null, msg);
        }
    }

    //remove this meal/setmeal from the shopping cart
    public void removeFromList(ActionEvent event) {
        if (meal != null && meal.getId() != null) {
            int pos = findPosition(meal);
            choiceList.get(pos).remove(meal);
            discountBill -= priceList.get(meal.getId()) * quantityList.get(meal.getId());
            totalBill -= meal.getOriginalPrice() * quantityList.get(meal.getId());
            quantityList.remove(meal.getId());
            reservation.setDeposit(computeDeposit());
            FacesMessage msg = new FacesMessage("Removed: ", meal.getName());
            FacesContext.getCurrentInstance().addMessage(null, msg);
        }
    }

    //change the quantity of selected meal/set meal purchased
    public void changeValueInList(ActionEvent event) {
        if (meal != null && meal.getId() != null) {
            if (quantity != 0) {
                int previousQuantity = quantityList.get(meal.getId());
                quantityList.remove(meal.getId());
                quantityList.put(meal.getId(), quantity);
                discountBill += priceList.get(meal.getId()) * (quantityList.get(meal.getId()) - previousQuantity);
                totalBill += meal.getOriginalPrice() * (quantityList.get(meal.getId()) - previousQuantity);
                reservation.setDeposit(computeDeposit());
                FacesMessage msg = new FacesMessage("Changed quantity: " + meal.getName() + " from " + previousQuantity + " to " + quantity);
                FacesContext.getCurrentInstance().addMessage(null, msg);
            } else {
                FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("You can't change the quantity to 0"));
            }
        }
    }

    //return the position of this meal in the list of food type. In case it's a set meal
    //then return the position =  numOfType
    private int findPosition(FnBProductEntity meal) {
        if (meal instanceof MealEntity) {
            return getPosition(((MealEntity) meal).getType().getName());
        }

        return numOfType;
    }

    //Check whether the tourist has selected this meal/setmeal
    public boolean checkContain() {
        if (meal != null && meal.getId() != null) {
            int pos = findPosition(meal);
            if (choiceList.get(pos).contains(meal)) {
                return true;
            }
        }
        return false;
    }
    //end method for meal and set meal reservation

    //methods for table reservation
    public void initTableList(ActionEvent event) {
        String temp = FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("branchId") + "";
        branchId = Long.parseLong(temp.trim());
        generateTable(0, 0, 23, 3);
    }

    //Generate list of time slots we can select
    private void generateTable(int startHour, int startMinute, int endHour, int endMinute) {
        finish = false;
        createTable = false;
        setTableList(reservationSessionBean.getTableList(branchId));

        capacity = new int[getTableList().size()][24][4];
        quantityAvail = new List[tableList.size()];
        for (int i = 0; i < quantityAvail.length; i++) {
            quantityAvail[i] = new ArrayList();
        }

        selectedQuantity = new int[tableList.size()];
        boolean[][] avail = new boolean[24][4];

        for (int i = 0; i < getTableList().size(); i++) {
            capacity[i] = reservationSessionBean.getAvailTable(getTableList().get(i).getId(), getReservation().getDateReserved());
        }

        for (int i = 0; i < 24; i++) {
            for (int j = 0; j < 4; j++) {
                int sum = 0;
                for (int k = 0; k < getTableList().size(); k++) {
                    sum += capacity[k][i][j] * getTableList().get(k).getUnitCapacity();
                }
                if (sum >= getReservation().getNoOfPeople()) {
                    avail[i][j] = true;
                }
            }
        }

        interval = reservationSessionBean.getIntervalByBranchId(branchId);

        generateAvailList(avail, startHour, startMinute, endHour, endMinute);
    }

    private void generateAvailList(boolean[][] avail, int startHour, int startMinute, int endHour, int endMinute) {
        setAvailSlot(new ArrayList());

        int count = 0;

        while (startHour <= endHour && startMinute <= endMinute) {
            System.out.println("StartHour is " + startHour + " and start minutes is " + startMinute);
            if (avail[startHour][startMinute]) {
                count++;
            } else {
                count = 0;
            }

            if (count >= interval) {
                int temp = startHour * 4 + startMinute - interval + 1;
                getAvailSlot().add(new SelectItem(temp, ((temp / 4) < 10 ? "0" : "") + temp / 4
                        + ":" + ((temp % 4) * 15) + (((temp % 4) * 15) == 0 ? "0" : "")));
            }

            startMinute++;
            if (startMinute == 4) {
                startMinute = 0;
                startHour++;
            }
        }

        if (availSlot.isEmpty()) {
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,
                    "Sorry we don't have enough capacity for this time", ""));
        }
    }

    //compute the available quantity for each table type on a specific date.
    public void chooseTableType(ActionEvent event) throws IOException {
        createTable = true;
        int hour = timeInput / 4;
        int minute = timeInput % 4;
        reservation.setTimeBegin(new Time(hour, minute * 15, 0));
        reservation.setTimeEnd(new Time(hour + interval / 4, (minute + interval % 4) * 15, 0));
        populateTableList(hour, minute);
    }

    private void populateTableList(int hour, int minute) {
        tablePos = new HashMap<Long, Integer>();
        for (int i = 0; i < getTableList().size(); i++) {
            int min = Integer.MAX_VALUE;

            for (int j = 0; j < interval; j++) {
                int temp = minute + j;
                if (min > capacity[i][hour + temp / 4][temp % 4]) {
                    min = capacity[i][hour + temp / 4][temp % 4];
                }
            }
            for (int k = 0; k <= min + getCurrentReservedQuantity(tableList.get(i)); k++) {
                quantityAvail[i].add(new SelectItem(k, k + ""));
            }
            tablePos.put(tableList.get(i).getId(), i);
        }
    }

    /*Methods for change reservation
     * I)Table Type:
     * 1)Select the begin and end time from reservation
     * 2)Catch the branchId from reservation
     * 3)Generate the list of tables in this branch for this specific time slot
     * 4)Populate the tablePos (used to find the position of table in the list)
     * 5)Compute the maximum number of table that we can select for each type
     * 6)Populate the current reserved table for each type in this reservation
     */
    //TODO: force the tourist to make the payment before any changes can be applied to the
    //database
    public void confirmChangeReservationInfo(ActionEvent event) throws IOException {
        if (checkEnoughSeat()) {
            double newDeposit = computeDeposit();
            double difference = newDeposit - previousDeposit;
            double amountPaid = branchSessionBean.getChangeFeeByBranchId(branchId);

            if (difference > 0) {
                amountPaid += difference;
                reservation.setDeposit(newDeposit);
            } else {
                reservation.setDeposit(previousDeposit);
            }

            if (amountPaid > 0) {

                setInfoForPayment(amountPaid, "VisitorViewReservationInfo.xhtml", "changeReservation");
            }
            changeReservation = true;
        }
    }

    private void setInfoForPayment(double amountPaid, String url, String purpose) throws IOException {
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("amountPaid", amountPaid);
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("tourist", tourist);
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("selectedReservation", reservation);
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("endPaymentUrl", url);
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("purpose", purpose);
        FacesContext.getCurrentInstance().getExternalContext().redirect("IAPaymentProcessing.xhtml");
    }

    public void changeReservationInfo(ActionEvent event) throws IOException {

        //populate the table info
        if (previousReservationId == 0 || previousReservationId != reservation.getId()) {
            Time begin = reservation.getTimeBegin();
            Time end = reservation.getTimeEnd();
            branchId = reservation.getBranch().getId();
            generateTable(begin.getHours(), begin.getMinutes() / 15, end.getHours(), end.getMinutes() / 15);

            populateTableList(begin.getHours(), begin.getMinutes() / 15);
            //end of populate the table info

            //retrieve the meal info from branch
            setListOfFoodType(productSessionBean.getFoodType());
            populateProductList();
            //end retrieve meal info from branch

            previousReservationId = reservation.getId();
            populateCurrentReservedQuantity();

            for (int i = 0; i < listOfReservedSetMeal.size(); i++) {
                ReservedSetMealEntity res = listOfReservedSetMeal.get(i);
                SetMealEntity set = res.getSetMeal();
                choiceList.get(choiceList.size() - 1).add(set);
                quantityList.put(set.getId(), res.getQuantity());
            }

            for (int i = 0; i < listOfReservedMeal.size(); i++) {
                ReservedMealEntity res = listOfReservedMeal.get(i);
                MealEntity tempMeal = res.getMeal();
                int pos = findPosition(tempMeal);
                choiceList.get(pos).add(tempMeal);
                quantityList.put(tempMeal.getId(), res.getQuantity());
            }
        }
        setPreviousDeposit(reservation.getDeposit());

        //end populate the reserved meal and set meal
      
    }

    //populate the number of tables reserved for each type into the selected quantity array
    private void populateCurrentReservedQuantity() {
        for (int i = 0; i < listOfReservedTable.size(); i++) {
            ReservedTableEntity res = listOfReservedTable.get(i);
            long tableId = res.getTableType().getId();
            int pos = tablePos.get(tableId);
            selectedQuantity[pos] = res.getQuantity();
        }
    }

    //get the number of tables reserved for this type
    private int getCurrentReservedQuantity(TableTypeEntity table) {
        if (listOfReservedTable != null) {
            for (ReservedTableEntity res : listOfReservedTable) {
                if (res.getTableType().equals(table)) {
                    return res.getQuantity();
                }
            }
        }
        return 0;
    }

    //end method for change reservation
    public void confirmTableTypeWithoutMeal(ActionEvent event) throws IOException {
        if (checkEnoughSeat()) {
            String serverName = ctx.getExternalContext().getRequestServerName();
            int serverPort = (Integer) ctx.getExternalContext().getSessionMap().get("serverPort");
            String contextPath = ctx.getExternalContext().getRequestContextPath();
            ctx.getExternalContext().redirect("http://" + serverName + ":" + serverPort + contextPath + "/tourist/VisitorConfirmReservation.xhtml");
        }
    }

    public void confirmTableTypeWithMeal(ActionEvent event) throws IOException {
        if (checkEnoughSeat()) {
            quantityList = new HashMap<Long, Integer>();
            String serverName = ctx.getExternalContext().getRequestServerName();
            int serverPort = (Integer) ctx.getExternalContext().getSessionMap().get("serverPort");
            String contextPath = ctx.getExternalContext().getRequestContextPath();
            ctx.getExternalContext().redirect("http://" + serverName + ":" + serverPort + contextPath + "tourist/VisitorSelectMeal.xhtml");
        }
    }

    private boolean checkEnoughSeat() {
        int sum = 0;
        for (int i = 0; i < tableList.size(); i++) {
            sum += selectedQuantity[i] * tableList.get(i).getUnitCapacity();
        }

        if (sum < reservation.getNoOfPeople()) {
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,
                    "You only select " + sum + " seats while you have " + reservation.getNoOfPeople() + " people", ""));
            return false;
        }
        return true;
    }

    public String getReservedTime() {
        return ((timeInput / 4) < 10 ? "0" : "") + timeInput / 4
                + ":" + ((timeInput % 4) * 15) + (((timeInput % 4) * 15) == 0 ? "0" : "");
    }

    public String getReservedDate() {
        return reservation.getDateReserved().getDate() + "/" + (reservation.getDateReserved().getMonth() + 1)
                + "/" + (reservation.getDateReserved().getYear() + 1900);
    }

    //end method for table reservation
    public TouristAccountEntity getTourist() {
        return tourist;
    }

    public void setTourist(TouristAccountEntity tourist) {
        this.tourist = tourist;
    }

    public List<ProductEntity> getListOfProduct() {
        return listOfProduct;
    }

    public void setListOfProduct(List<ProductEntity> listOfProduct) {
        this.listOfProduct = listOfProduct;
    }

    public List<List<FnBProductEntity>> getListOfCategory() {
        return listOfCategory;
    }

    public void setListOfCategory(List<List<FnBProductEntity>> listOfCategory) {
        this.setListOfCategory(listOfCategory);
    }

    public List<FoodTypeEntity> getListOfFoodType() {
        return listOfFoodType;
    }

    public void setListOfFoodType(List<FoodTypeEntity> listOfFoodType) {
        this.listOfFoodType = listOfFoodType;
    }

    public FnBProductEntity[] getSelectedProduct() {
        return selectedProduct;
    }

    public void setSelectedProduct(FnBProductEntity[] selectedProduct) {
        this.selectedProduct = selectedProduct;
    }

    public List<List<FnBProductEntity>> getChoiceList() {
        return choiceList;
    }

    public void setChoiceList(List<List<FnBProductEntity>> choiceList) {
        this.choiceList = choiceList;
    }

    public int getPosition(String name) {
        return foodMap.get(name);
    }

    public double getPrice() {
        if (meal != null && meal.getId() != null) {
            if (priceList != null) {
                return priceList.get(meal.getId());
            } else {
                return 0;
            }
        }
        return 0;
    }

    public double getPrice1(long id) {
        return priceList.get(id);
    }

    public int getSpecificQuantity(long id) {
        return quantityList.get(id);
    }

    public FnBProductEntity getMeal() {
        if (meal != null) {
            return meal;
        }

        return new MealEntity();
    }

    //let the tourist finish his booking
    //TODO: force him to make payment first before making the reservation
    public void makeReservation(ActionEvent event) throws IOException {
        setTourist((TouristAccountEntity) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("tourist"));
        reservation.setDeposit(computeDeposit());
        makeReservation = true;
        System.out.println("Before making payment");
        setInfoForPayment(reservation.getDeposit(), "VisitorConfirmReservation.xhtml", "makeReservation");
    }

    //compute the amount of deposit
    private double computeDeposit() {
        double depositTemp = 0;

        for (int i = 0; i < choiceList.size() - 1; i++) {
            for (FnBProductEntity p : choiceList.get(i)) {
                MealEntity mealTemp = (MealEntity) p;
                int quantityTemp = 0;
                if (quantityList != null) {
                    quantityTemp = quantityList.get(mealTemp.getId());
                }
                double onlinePrice = 0;
                if (priceList != null) {
                    onlinePrice = priceList.get(mealTemp.getId());
                }
                depositTemp += onlinePrice * quantityTemp * mealTemp.getDeposit() / 100;
            }
        }

        if (!choiceList.isEmpty()) {
            for (FnBProductEntity p : choiceList.get(choiceList.size() - 1)) {
                SetMealEntity set = (SetMealEntity) p;
                int quantityTemp = 0;
                if (quantityList != null) {
                    quantityTemp = quantityList.get(set.getId());
                }
                double onlinePrice = 0;
                if (priceList != null) {
                    onlinePrice = priceList.get(set.getId());
                }
                depositTemp += onlinePrice * quantityTemp * set.getDeposit() / 100;
            }
        }

        if (depositTemp == 0) {
            computeDepositForTable();
            return depositForTable;
        }
        return depositTemp;
    }

    //set the deposit fees in case the tourists don't purchase any meals nor
    //set meal online
    private void computeDepositForTable() {
        depositForTable = 0;
        for (int i = 0; i < selectedQuantity.length; i++) {
            depositForTable += selectedQuantity[i];
        }
        depositForTable *= 5;
    }

    public void changeTableInfo(ActionEvent event) {
        checkEnoughSeat();
        reservation.setDeposit(computeDeposit());
    }

    public void changeBasicInfo(ActionEvent event) {
        if (checkEnoughSeat()) {
            double newDeposit = reservation.getDeposit();
            reservation.setDeposit(previousDeposit);
            reservationSessionBean.updateBasicInfoOfReservation(reservation);
            reservation.setDeposit(newDeposit);
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
                    "You have successfully changed your basic info", ""));
        }
    }

    public void finishReservation(ActionEvent event) throws IOException {
        FacesContext.getCurrentInstance().getExternalContext().redirect("tourist/VisitorLogin.xhtml");
    }

    public void initVisitorConfirm(PhaseEvent event) throws MessagingException {
        if (makeReservation) {
            String paymentOutcome = (String) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("paymentOutcome");
            if (paymentOutcome != null) {
                if (paymentOutcome.equals("success")) {
                    long transactionId = (Long) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("transactionId");
                    String result = reservationSessionBean.addReservation(reservation, tourist.getId(), branchId, choiceList,
                            priceList, quantityList, tableList, selectedQuantity, transactionId);
                    FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
                            result, ""));
                    finish = true;
                    reloadPage = true;
                    reloadTable = true;
                    createTable = false;
                    timeInput = 0;
                    meal = new MealEntity();
                    availSlot = new ArrayList<SelectItem>();
                } else {
                    FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
                            FacesMessage.SEVERITY_ERROR, "You failed to change your reservation", ""));
                }
                FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove("paymentOutcome");
            }
            makeReservation = false;
        }
        reservation.setDeposit(computeDeposit());
    }

    public void initReservationList(PhaseEvent event) {
        setTourist((TouristAccountEntity) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("tourist"));

        listOfReservation = reservationSessionBean.getFnBReservationListByTouristId(tourist.getId());
    }

    public void onRowSelectNavigateSeeReservationInfo(SelectEvent event) throws IOException {
        reservation = (FnBReservationEntity) event.getObject();
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("reservation", reservation);
        String serverName = ctx.getExternalContext().getRequestServerName();
        int serverPort = (Integer) ctx.getExternalContext().getSessionMap().get("serverPort");
        String contextPath = ctx.getExternalContext().getRequestContextPath();
        ctx.getExternalContext().redirect("http://" + serverName + ":" + serverPort + contextPath + "/tourist/VisitorViewReservationInfo.xhtml");
    }

    public String getFormatReservedDate(Date date) {
        return date.getDate() + "/" + (date.getMonth() + 1)
                + "/" + (date.getYear() + 1900);
    }

    public String getFormatReservedTime(Time time) {
        int hour = time.getHours();
        int minute = time.getMinutes();
        return (hour < 10 ? "0" : "") + hour
                + ":" + minute + (minute == 0 ? "0" : "");
    }

    public void initVisitorViewReservationInfo(PhaseEvent event) {
        //this one is to change the reservation info after the visitor has made the payment successfully
        if (changeReservation) {
            System.out.println("Begin to change reservation");
            String paymentOutcome = (String) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("paymentOutcome");
            System.out.println("after get payment outcome");
            if (paymentOutcome != null) {
                if (paymentOutcome.equals("success")) {
                    reservationSessionBean.changeReservation(reservation, choiceList,
                            priceList, quantityList, tableList, selectedQuantity);
                    FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
                            FacesMessage.SEVERITY_INFO, "You have successfully changed your reservation", ""));
                } else {
                    FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
                            FacesMessage.SEVERITY_ERROR, "You failed to change your reservation", ""));
                }
                FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove("paymentOutcome");
            }
            changeReservation = false;
        }
        System.out.println("1 ");
        if (reservation == null) {
            reservation = (FnBReservationEntity) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("reservation");
        }
        //this part is to reload the info about reserved table, meal and set meals for this reservation
        listOfReservedTable = reservationSessionBean.getReservedTableByReservationId(reservation.getId());
        System.out.println("a");
        listOfReservedMeal = reservationSessionBean.getReservedMealByReservationId(reservation.getId());
        System.out.println("b");
        listOfReservedSetMeal = reservationSessionBean.getReservedSetMealByReservationId(reservation.getId());
        System.out.println(2);
        totalBill = 0;
        discountBill = totalBill;

        for (int i = 0; i < listOfReservedMeal.size(); i++) {
            totalBill += listOfReservedMeal.get(i).getMeal().getOriginalPrice() * listOfReservedMeal.get(i).getQuantity();
            discountBill += listOfReservedMeal.get(i).getPrice() * listOfReservedMeal.get(i).getQuantity();
        }
        System.out.println(3);

        for (int i = 0; i < listOfReservedSetMeal.size(); i++) {
            totalBill += listOfReservedSetMeal.get(i).getSetMeal().getOriginalPrice() * listOfReservedSetMeal.get(i).getQuantity();
            discountBill += listOfReservedSetMeal.get(i).getPrice() * listOfReservedSetMeal.get(i).getQuantity();
        }
        System.out.println(4);
        reloadTable = true;
    }

    public void selectReservation(SelectEvent event) {
        FnBReservationEntity res = (FnBReservationEntity) event.getObject();
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("reservation", res);
    }

    public void setMeal(MealEntity meal) {
        this.meal = meal;
    }

    public Map<Long, Integer> getQuantityList() {
        return quantityList;
    }

    public void setQuantityList(Map<Long, Integer> quantityList) {
        this.quantityList = quantityList;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public double getTotalBill() {
        return totalBill;
    }

    public void setTotalBill(double totalBill) {
        this.totalBill = totalBill;
    }

    public double getDiscountBill() {
        return discountBill;
    }

    public void setDiscountBill(double discountBill) {
        this.discountBill = discountBill;
    }

    public FnBReservationEntity getReservation() {
        return reservation;
    }

    public void setReservation(FnBReservationEntity reservation) {
        this.reservation = reservation;
    }

    public List getAvailSlot() {
        return availSlot;
    }

    public void setAvailSlot(List availSlot) {
        this.availSlot = availSlot;
    }

    public List<TableTypeEntity> getTableList() {
        return tableList;
    }

    public void setTableList(List<TableTypeEntity> tableList) {
        this.tableList = tableList;
    }

    public int[] getSelectedQuantity() {
        return selectedQuantity;
    }

    public void setSelectedQuantity(int[] selectedQuantity) {
        this.selectedQuantity = selectedQuantity;
    }

    public List[] getQuantityAvail() {
        return quantityAvail;
    }

    public void setQuantityAvail(List[] quantityAvail) {
        this.quantityAvail = quantityAvail;
    }

    public int getIndex() {
        index = selectedQuantity[count1];
        count1 = (count1 + 1) % tableList.size();
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
        selectedQuantity[count] = index;
        count = (count + 1) % tableList.size();
    }

    public int getTimeInput() {
        return timeInput;
    }

    public void setTimeInput(int timeInput) {
        this.timeInput = timeInput;
    }

    public boolean isFinish() {
        return finish;
    }

    public void setFinish(boolean finish) {
        this.finish = finish;
    }

    public boolean isCreateTable() {
        return createTable;
    }

    public void setCreateTable(boolean createTable) {
        this.createTable = createTable;
    }

    public double getDepositForTable() {
        return depositForTable;
    }

    public void setDepositForTable(double depositForTable) {
        this.depositForTable = depositForTable;
    }

    public int getTablePos(long id) {
        return tablePos.get(id);
    }

    public List<FnBReservationEntity> getListOfReservation() {
        return listOfReservation;
    }

    public void setListOfReservation(List<FnBReservationEntity> listOfReservation) {
        this.listOfReservation = listOfReservation;
    }

    public List<ReservedTableEntity> getListOfReservedTable() {
        return listOfReservedTable;
    }

    public void setListOfReservedTable(List<ReservedTableEntity> listOfReservedTable) {
        this.listOfReservedTable = listOfReservedTable;
    }

    public List<ReservedMealEntity> getListOfReservedMeal() {
        return listOfReservedMeal;
    }

    public void setListOfReservedMeal(List<ReservedMealEntity> listOfReservedMeal) {
        this.listOfReservedMeal = listOfReservedMeal;
    }

    public List<ReservedSetMealEntity> getListOfReservedSetMeal() {
        return listOfReservedSetMeal;
    }

    public void setListOfReservedSetMeal(List<ReservedSetMealEntity> listOfReservedSetMeal) {
        this.listOfReservedSetMeal = listOfReservedSetMeal;
    }

    public String generateSpace(FnBProductEntity product) {
        int countTemp = product.getName().length();
        countTemp += (quantityList.get(product.getId()) + "").length();
        String result = "";
        for (int i = 0; i < 25 - countTemp; i++) {
            result += " ";
        }
        return result;
    }

    public boolean checkChangeEligible() {
        Date dateTemp = new Date();
        Date res = new Date(reservation.getDateReserved().getTime() + 24 * 60 * 60 * 1000);

        return res.compareTo(dateTemp) < 0 || reservation.getConfirmation().equals("Cancelled");
    }

    public void cancelReservation(ActionEvent event) throws IOException {
        if (!checkChangeEligible()) {
            reservationSessionBean.cancelReservation(reservation.getId());
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
                    FacesMessage.SEVERITY_INFO, "You have successfully cancelled this reservation", ""));
            listOfReservation = reservationSessionBean.getFnBReservationListByTouristId(tourist.getId());
            reservation = (FnBReservationEntity) reservationSessionBean.getReservationById(reservation.getId());
        }
    }

    public double getPreviousDeposit() {
        return previousDeposit;
    }

    public void setPreviousDeposit(double previousDeposit) {
        this.previousDeposit = previousDeposit;
    }

    public String getChangeFee() {
        return branchSessionBean.getChangeFeeByBranchId(branchId) + "";
    }
}
